package com.example.thread;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

// 这是是测试的代码，用来测试自己的线程池是否好用
public class TestMySelfThreadPool {
    private static final int TASK_NUM = 50;//任务的个数

    public static void main(String[] args) {
        // 创建线程池，3个工作线程，50个任务
        MySelfThreadPool myPool = new MySelfThreadPool(3, 50);
        // 50个任务怎么执行呢？直接调用myPool的execute方法来分别执行。
        for (int i = 0; i < TASK_NUM; i++) {
            // execute是线程池里面封装的方法。隐藏了细节。反正就是用线程池调这个方法就好。具体由哪个线程执行我们不关注。
            // new MyTask("task_" + i)就是我们线程池使用者传进去要执行的任务了。任务其实很好理解，就是一串要执行的代码，只是用Runable标准格式包装了一下而已。
            myPool.execute(new MyTask("task_" + i));
        }
    }

    // 这是实际要执行的任务。这里用的是一个内部类的写法，但不是匿名内部类写法。平时我们习惯多用匿名内部类写法。
    static class MyTask implements Runnable {
        // 任务名称
        private String name;

        // 构造函数
        public MyTask(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                // 假装模拟任务执行需要的时间
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 具体任务就是，打印一段话： xx任务结束。
            System.out.println("task :" + name + " end...");
        }

        // 不知道这句话具体有啥用。估计是如果打印MyTask的实例对象的时候，为了不打印出对象的内存地址，必须要在类里面重写toString方法。
        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "name = " + name;
        }

    }
}


// 本例演示的是，自己手动实现一个线程池。
// 据说也是常常要手写的面试题。本案例只是一个简单的实现，没有核心线程、阻塞队列、最大线程数量、饱和策略等体现出来。
class MySelfThreadPool {

    // 类变量区
    //默认线程池中的线程的数量。核心线程数量。
    private static final int WORK_NUM = 5;
    //默认处理任务的数量。
    private static final int TASK_NUM = 100;

    // 实例变量区
    private int workNum;//线程数量
    private int taskNum;//任务数量
    private final Set<WorkThread> workThreads;//保存线程对象的集合
    private final BlockingQueue<Runnable> taskQueue;//阻塞有序队列存放任务

    // 无参构造方法
    public MySelfThreadPool() {
        // 在Java中，this()是一个特殊的构造函数调用，它用于在当前类中调用另一个构造函数。
        // 当你在一个构造函数中使用this()时，它会调用同一个类中的另一个构造函数，并且必须作为构造函数中的第一条语句。
        // 这种方式允许你在不同的构造函数中重用代码，并且能够保持代码的简洁和一致性。
        this(WORK_NUM, TASK_NUM); // 无参的时候，就是使用默认参数。
    }

    // 有参构造函数
    public MySelfThreadPool(int workNum, int taskNum) { // 构造线程池。workNum=3，taskNum=50
        if (workNum <= 0) workNum = WORK_NUM; // 传入值<=0算是非法值，那就只能一点给它设置默认值喽。
        if (taskNum <= 0) taskNum = TASK_NUM;
        // 创建阻塞队列。ArrayBlockingQueue是java并发包中自带的阻塞队列的实现。本项目中我们也有自己手写实现阻塞队列的一个题目。
        taskQueue = new ArrayBlockingQueue<>(taskNum);  // taskNum是阻塞队列可以容纳的任务数。
        this.workNum = workNum; // 构造函数的局部参数保存到实例变量里面。
        this.taskNum = taskNum; // 构造函数的局部参数保存到实例变量里面。
        // 装工作线程的容器，我们用HashSet。set是单列的、不可重复的集合。为什么用hashset来装，在本页面最后的后记有讲。
        workThreads = new HashSet<>();
        //启动一定数量的线程数，从队列中获取任务处理
        for (int i = 0; i < workNum; i++) {
            // WorkThread是我们继承了Thread类之后，封装的一个子类。也是线程。"thead_" + i是线程名。
            WorkThread workThread = new WorkThread("thead_" + i);
            // 开启线程。
            // start()方法会告诉Java虚拟机启动一个新的线程，并且在新线程中调用workThread对象的run()方法。
            // run()方法中包含了线程实际执行的代码逻辑，当线程启动后，会执行run()方法中的代码。
            // 需要注意的是，不要直接调用run()方法来启动线程，因为这样会在当前线程的上下文中执行代码，而不会启动新的线程。
            // 正确的做法是通过调用start()方法来启动线程。
            workThread.start();
            // 把已经开启了的线程对象，装入线程容器
            workThreads.add(workThread);
        }
    }

    // 看，Runnable task原来是使用者在调用线程池的execute方法时外部传入的。
    public void execute(Runnable task) {
        try {
            // 我们这里的设计是，有任务来，直接放入阻塞队列。
            // 一定得用try包住吗？
            taskQueue.put(task);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    // 关闭线程池。
    public void destroy() {
        System.out.println("ready close thread pool...");
        // 装线程对象的容器为空，为什么直接return？workThreads == null是空指针判断。workThreads.isEmpty()是啥意思呢？
        if (workThreads == null || workThreads.isEmpty()) return;
        for (WorkThread workThread : workThreads) {
            // stopWork里面用的是interrupt();其实就是主动结束线程，执行这个方法之后，线程处于终止状态。
            workThread.stopWork();
            // 上面仅仅只是终止了线程。网上的帖子也有说，当线程处于终止状态时，它并不会立即回收。
            // 在这段代码中，workThread = null是一种尝试帮助垃圾回收器（GC）释放资源的做法。虽然在Java中，垃圾回收器通常会自动释放不再使用的对象，但手动将引用设置为null可以在某些情况下加速对象的释放。
            // 当然你把对象置空，也不意味着垃圾回收器就会立即回收，但是，还是会比不置空更快一点。
            workThread = null;//help gc
        }
        // 是清空HashSet中的所有元素，使其成为空集合。
        // 在线程池关闭或者不再需要某些线程时，调用workThreads.clear()可以快速清空HashSet中的所有工作线程对象，从而释放内存并确保不再有任何线程留在线程池中。
        workThreads.clear();
    }

    private class WorkThread extends Thread {
        // 构造函数
        public WorkThread(String name) {
            // super()是用来调用父类的构造函数的关键字。当一个子类的构造函数中使用super()时，它是显式地调用父类对应的构造方法。
            // 如果没有显式地调用，那么，它将会默认隐式调用父类的无参构造函数（如果存在)。如果父类不存在无参构造方法，系统会报错。其实，在java里面，一个类再马虎，都至少必须要有一个无参构造方法的。
            // super()的作用是为了执行一些与父类相关的初始化操作。
            super();
            // name是线程的名字
            setName(name);
        }

        // 当调用WorkThread实例对象的start方法的时候，会自动调用这里吗？？？
        @Override
        public void run() {
            // 检查方法是否中断，并清除中断状态。
            while (!interrupted()) {
                try {
                    //获取队列里面的Runnable
                    Runnable runnable = taskQueue.take();
                    if (runnable != null) {
                        // 打印Runnable对象。
                        System.out.println(getName() + " readyexecute:" + runnable.toString());
                        // 要想执行Runnable，直接点它的run方法就行
                        runnable.run();
                    }
                    runnable = null;//help gc
                } catch (Exception e) {
                    interrupt();
                    e.printStackTrace();
                }
            }
        }

        public void stopWork() {
            interrupt();
        }
    }
}

// 后记：
// 在使用HashSet作为装工作线程对象的容器时，有几个好处：
// 在使用HashSet作为装工作线程对象的容器时，有几个好处：
//1. 唯一性：HashSet保证其中的元素是唯一的，这意味着你不会意外地添加重复的线程对象。这对于管理线程集合非常有用，因为你不需要额外的逻辑来处理重复元素的情况。
//2. 快速查找：HashSet提供了快速的查找操作，其查找时间复杂度是O(1)。这意味着你可以快速地检查一个线程是否已经在集合中，而不必遍历整个集合。以下是API:
// workThreads.contains(xx线程对象),此方法返回的是布尔，结果可用于if条件。
//3. 散列分布：HashSet内部使用了散列函数来分布元素，这使得元素在内部存储结构中的分布更加均匀。这有助于在大型集合中快速查找元素(说白了，还是为2服务的)。
//综上所述，使用HashSet作为装工作线程对象的容器可以提供唯一性、快速查找和散列分布等优势，使得线程管理更加高效。

// interrupted()方法是干嘛的
// Thread.interrupted()是一个静态方法，用于检查当前线程是否被中断，并清除中断状态。具体来说，Thread.interrupted()会返回一个boolean值，表示当前线程是否被中断，并且会清除当前线程的中断状态（即将中断状态置为false）。
//这个方法通常在多线程编程中被用来检查当前线程的中断状态，并且根据需要采取相应的操作。例如，可以在线程的执行过程中周期性地检查线程是否被中断，并在中断状态被设置时做出相应的处理，比如停止线程的执行、释放资源等。
//需要注意的是，Thread.interrupted()方法检查的是当前线程的中断状态，而不是调用这个方法的线程对象的中断状态。
